#  Twitter in your Profile - Pidgin plugin
#    by Alan Lynn, September 2008
#
#  This plugin is released under the GNU General Public License
#  (http://www.gnu.org/licenses/gpl.html).
#
#  If you release a modified version of this plugin, you should:
#    1. In the plugin info: List me as an author or give me credit in the description.
#    2. Give me credit in the source code and the readme.

use Purple;
use XML::XPath;
use XML::XPath::XMLParser;
use Date::Parse;
use Date::Format;

%PLUGIN_INFO = (
	perl_api_version => 2,
	name => "Twitter in your Profile",
	version => "1.3.1",
	summary => "Put your Twitter history into your profile.",
	description => "How to Use:\n\n1. In your profile (Accounts:Set User Info), put [twitter] where you want your Twitter history to appear. Use [twitter 3], or any other number to limit the number of statuses that appear.\n\n2. Enable the plugin \"Twitter in your Profile\". The plugin preferences should pop up automatically. Enter your Twitter user name then click \"Save and Update\". If you need to get to the preferences again later, click \"Configure Plugin\" from the plugins window.\n\n3. Do \"Get Info\" on yourself to confirm if it worked.\n\nIn the buddy list menu under \"Tools:Twitter in your profile\" there is an item \"Update Now\". Use this if you ever want to update your twitter history immediately.\n\n\ntime format:\nsee http://search.cpan.org/dist/TimeDate-1.16/lib/Date/Format.pm#CONVERSION_SPECIFICATION\n\nline format:\nKeywords in brackets are replaced with the corresponding information. Valid keywords are [time], [id], [text], [source], [in_reply_to_status_id], [in_reply_to_user_id], and [name].",
	author => 'Alan Lynn',
	url => "http://code.google.com/p/pidgin-twitter-profile",
	
	load => "plugin_load",
	unload => "plugin_unload",
	plugin_action_sub=> "plugin_actions_cb",
	prefs_info => "prefs_dialog"
);

%plugin_actions = (
	"Update Now" => \&update_now
);

sub plugin_init
{
	return %PLUGIN_INFO;
}

sub plugin_load
{
	our $plugin = shift;
	our $debug_name = "twitter_in_your_profile";
	our $pref_path = "/plugins/core/twitter-in-your-profile";
	our $twitter_url = "http://twitter.com/statuses/user_timeline/$twitter_name.xml";
	our $agent = "pidgin-twitter-profile/" . $PLUGIN_INFO{"version"};
	debug_info($agent);
	our $have_updated = 0;
	our $setting_user_info = 0;
	
	# initialize the preferences
	Purple::Prefs::add_none($pref_path);
	add_string_pref("twitter_name", "");
	add_int_pref("update_interval", 10);
	add_string_pref("time_format", '%I:%M %p %B %d, %Y');
	add_string_pref("line_format", '<a href="http://twitter.com/[name]"><b>[name]</b></a> [text] <a href="http://twitter.com/[name]/statuses/[id]">[time]</a> from [source]');
	add_string_pref("line_separator", '<hr>');
	
	# listen for signals
	$accounts_handle = Purple::Accounts::get_handle();
	Purple::Signal::connect($accounts_handle, "account-set-info", $plugin, \&set_user_info_cb, "");
	$connections_handle = Purple::Connections::get_handle();
	Purple::Signal::connect($connections_handle, "signed-on", $plugin, \&signed_on_cb, "");
	
	# popup prefences dialog if no twitter name is set
	if(!get_string_pref("twitter_name"))
	{
		prefs_dialog();
		return;
	}
	
	# do an update 60 seconds after the plugin is loaded
	schedule_update(60);
}

sub get_twitter_count
# returns the number of twitter status an account uses; zero if not applicable
{
	my $account = shift;
	
	$info = $account->get_user_info();
	# check for a match of [twitter] or [twitter X]
	if($info =~ m/\[twitter(?: (\d+))?\]/i)
	{
		if($1)
		{
			# [twitter X] => count = X
			return $1;
		}
		else
		{
			# [twitter] => default to count = 5
			return 5;
		}
	}
	else
	{
		return 0;
	}
}

sub contains_twitter
# returns true if the account's user info should contain twitter history
{
	my $account = shift;
	return get_twitter_count($account) > 0;
}

sub begin_update
{
	# do an update again in update_interval minutes
	$update_interval = get_int_pref("update_interval");
	if($update_interval and $update_interval != 0)
	{
		schedule_update(get_int_pref("update_interval") * 60);
	}
	
	$twitter_name = get_string_pref("twitter_name");
	# make sure all the necessary information is set
	if(!$twitter_name)
	{
		debug_info("can't update: no twitter name specified");
		return;
	}
	
	# search the accounts for the max number of statuses required
	@accounts = Purple::Accounts::get_all_active();
	$max_count = 0;
	$an_account_is_connected = 0;
	foreach $account (@accounts)
	{
		my $cur_count = get_twitter_count($account);
		if($cur_count > $max_count)
		{
			$max_count = $cur_count;
		}
		
		if($account->is_connected() && $cur_count > 0)
		{
			$an_account_is_connected = 1;
		}
	}
	
	if($max_count == 0)
	{
		debug_info("[twitter] not found in any accounts: skipping update");
		return;
	}
	elsif(!$an_account_is_connected)
	{
		debug_info("no accounts are connected: skipping update");
		return;
	}
	
	# fetch twitter xml
	debug_info("updating Twitter history");
	$twitter_url = "http://twitter.com/statuses/user_timeline/$twitter_name.xml?count=$max_count";
	Purple::Util::fetch_url($plugin, $twitter_url, TRUE, $agent, TRUE, "fetch_url_cb");
	
	return FALSE;
}

sub fetch_url_cb
{
	my $response = shift;
	#Purple::Debug::info("response", "$response\n");
	if(!$response)
	{
		debug_info("no response from twitter -> skip this update");
		return;
	}
	
	# parse xml for status data
	my $xp = XML::XPath->new(xml => $response);
	my $nodeset = $xp->find('/statuses/status');
	
	@output = ();
	foreach my $node ($nodeset->get_nodelist)
	{
		$time = $node->find("created_at")->string_value;
		$id = $node->find("id")->string_value;
		$text = $node->find("text")->string_value;
		$source = $node->find("source")->string_value;
		$in_reply_to_status_id = $node->find("in_reply_to_status_id")->string_value;
		$in_reply_to_user_id = $node->find("in_reply_to_user_id")->string_value;
		$favorited = $node->find("favorited")->string_value;
		
		# change the time to local time and reformat it
		$time = time2str(get_string_pref("time_format"), str2time($time));
		
		# linkify user names in replies
		if($in_reply_to_status_id)
		{
			$text =~ s/^\@(\w+)/\@<a href="http:\/\/twitter.com\/$1\/statuses\/$in_reply_to_status_id">$1<\/a>/;
		}
		else
		{
			$text =~ s/^\@(\w+)/\@<a href="http:\/\/twitter.com\/$1">$1<\/a>/;
		}
		
		# do line formatting
		$line = get_string_pref("line_format");
		$line =~ s/\[time\]/$time/gi;
		$line =~ s/\[id\]/$id/gi;
		$line =~ s/\[text\]/$text/gi;
		$line =~ s/\[source\]/$source/gi;
		$line =~ s/\[in_reply_to_status_id\]/$in_reply_to_status_id/gi;
		$line =~ s/\[in_reply_to_user_id\]/$in_reply_to_user_id/gi;
		$twitter_name = get_string_pref("twitter_name");
		$line =~ s/\[name\]/$twitter_name/gi;
		
		push(@output, $line);
	}
	
	$line_separator = get_string_pref("line_separator");
	
	# put output into each account with [twitter] in user info
	$setting_user_info = 1;
	@accounts = Purple::Accounts::get_all_active();
	foreach $account (@accounts)
	{
		if($account->is_connected())
		{
			$num_lines = get_twitter_count($account);
			if($num_lines > 0)
			{
				# don't try to use more lines than there were statuses
				if($num_lines > scalar(@output))
				{
					$num_lines = scalar(@output);
				}
				$this_output = $line_separator . join($line_separator, @output[0..$num_lines-1]) . $line_separator;
				# replace [twitter] with the output
				$info =~ s/\[twitter(?: (\d+))?\]/$this_output/i;
				# set the user info
				$connection = $account->get_connection();
				Purple::Serv::set_info($connection, $info);
			}
		}
	}
	$setting_user_info = 0;	
	$have_updated = 1;
}

sub plugin_actions_cb
{
	my @actions = ("Update Now");
}

sub update_now
{
	# user clicked "Update Now"
	cancel_update();
	begin_update($plugin);
}

sub schedule_update
{
	my $wait_time = shift;
	cancel_update();
	our $update_handle = Purple::timeout_add($plugin, $wait_time, \&begin_update, $plugin);
}

sub cancel_update
{
	if($update_handle)
	{
		Purple::timeout_remove($update_handle);
	}
}

sub signed_on_cb
{
	if(!$have_updated)
	{
		@accounts = Purple::Accounts::get_all();
		foreach $account (@accounts)
		{
			if(!$account->is_connected() and contains_twitter($account))
			{
				# an account that has twitter in it is not connected yet
				return;
			}
		}
		# all accounts with twitter in them are connected
		update_now();
	}
}

sub set_user_info_cb
{
	my $setting_account = shift;
	if(!$setting_user_info and contains_twitter($setting_account))
	{
		# info was set by user, not by plugin
		update_now();
	}
}

sub prefs_dialog
{
	# use a request instead of the standard plugin preferences dialog
	$basic_group = Purple::Request::Field::Group::new("Basic Info");
	
	$field = Purple::Request::Field::string_new("twitter_name", "Twitter user name", get_string_pref("twitter_name"), 0);
	Purple::Request::Field::Group::add_field($basic_group, $field);
	
	$field = Purple::Request::Field::int_new("update_interval", "update frequency, in minutes", get_int_pref("update_interval"));
	Purple::Request::Field::Group::add_field($basic_group, $field);
	
	
	$formatting_group = Purple::Request::Field::Group::new("Formatting");
	
	$field = Purple::Request::Field::string_new("time_format", "time format", get_string_pref("time_format"), 0);
	Purple::Request::Field::Group::add_field($formatting_group, $field);
	
	$field = Purple::Request::Field::string_new("line_format", "line format", get_string_pref("line_format"), 1);
	Purple::Request::Field::Group::add_field($formatting_group, $field);
	
	$field = Purple::Request::Field::string_new("line_separator", "line separator", get_string_pref("line_separator"), 0);
	Purple::Request::Field::Group::add_field($formatting_group, $field);
	
	
	$request = Purple::Request::Fields::new();
	Purple::Request::Fields::add_group($request, $basic_group);
	Purple::Request::Fields::add_group($request, $formatting_group);
	
	# show the dialog
	Purple::Request::fields(
		$plugin,
		"Plugin Preferences",
		"Twitter in your Profile - Preferences",
		undef,
		$request,
		"Save and Update", "save_prefs_cb",
		"Cancel", "cancel_prefs_cb"
	);
}

sub save_prefs_cb
{
	$fields = shift;
	
	save_string_pref("twitter_name", $fields);
	save_int_pref("update_interval", $fields);
	save_string_pref("time_format", $fields);
	save_string_pref("line_format", $fields);
	save_string_pref("line_separator", $fields);
	
	schedule_update(2);
}

sub add_string_pref
{
	my $pref_name = $_[0];
	my $pref_val = $_[1];
	Purple::Prefs::add_string($pref_path . "/" . $pref_name, $pref_val);
}

sub add_int_pref
{
	my $pref_name = $_[0];
	my $pref_val = $_[1];
	Purple::Prefs::add_int($pref_path . "/" . $pref_name, $pref_val);
}

sub get_string_pref
{
	my $pref_name = shift;
	return Purple::Prefs::get_string($pref_path . "/" . $pref_name);
}

sub get_int_pref
{
	my $pref_name = shift;
	return Purple::Prefs::get_int($pref_path . "/" . $pref_name);
}

sub set_string_pref
{
	my $pref_name = $_[0];
	my $pref_value = $_[1];
	Purple::Prefs::set_string($pref_path . "/" . $pref_name, $pref_value);
}

sub set_int_pref
{
	my $pref_name = $_[0];
	my $pref_value = $_[1];
	Purple::Prefs::set_int($pref_path . "/" . $pref_name, $pref_value);
}

sub save_string_pref
{
	my $pref_name = $_[0];
	my $fields = $_[1];
	my $pref_value = Purple::Request::Fields::get_string($fields, $pref_name);
	set_string_pref($pref_name, $pref_value);
}

sub save_int_pref
{
	my $pref_name = $_[0];
	my $fields = $_[1];
	my $pref_value = Purple::Request::Fields::get_integer($fields, $pref_name);
	set_int_pref($pref_name, $pref_value);
}

sub debug_info
{
	Purple::Debug::info($debug_name, shift . "\n");
}

sub plugin_unload
{
}
